package com.michaelbulava.TFS.tasks;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.vcsUtil.VcsUtil;
import com.michaelbulava.TFS.Messages;
import com.michaelbulava.TFS.configuration.ProjectPreferences;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.persistence.IdeaPersistenceStoreProvider;
import com.michaelbulava.TFS.ui.config.ClientConnectionAdvisor;
import com.michaelbulava.TFS.ui.dialogs.connect.CredentialsDialog;
import com.michaelbulava.TFS.ui.helpers.UIHelpers;
import com.michaelbulava.TFS.vcs.TFSVcs;
import com.microsoft.tfs.core.TFSConnection;
import com.microsoft.tfs.core.TFSTeamProjectCollection;
import com.microsoft.tfs.core.clients.versioncontrol.Workstation;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Workspace;
import com.microsoft.tfs.core.clients.versioncontrol.workspacecache.WorkspaceInfo;
import com.microsoft.tfs.core.config.ConnectionAdvisor;
import com.microsoft.tfs.core.credentials.CachedCredentials;
import com.microsoft.tfs.core.credentials.CredentialsManagerFactory;
import com.microsoft.tfs.core.exceptions.TFSAccessException;
import com.microsoft.tfs.core.exceptions.TFSFederatedAuthException;
import com.microsoft.tfs.core.exceptions.TFSUnauthorizedException;
import com.microsoft.tfs.core.httpclient.Credentials;
import com.microsoft.tfs.core.httpclient.DefaultNTCredentials;
import com.microsoft.tfs.core.httpclient.UsernamePasswordCredentials;
import com.microsoft.tfs.core.util.CredentialsUtils;
import com.microsoft.tfs.core.util.serverlist.ServerListCollectionEntry;
import com.microsoft.tfs.core.util.serverlist.ServerListConfigurationEntry;
import com.microsoft.tfs.core.util.serverlist.ServerListManager;
import com.microsoft.tfs.core.util.serverlist.ServerListManagerFactory;
import com.microsoft.tfs.core.ws.runtime.exceptions.TransportRequestHandlerCanceledException;
import com.microsoft.tfs.jni.helpers.LocalHost;
import com.microsoft.tfs.util.Check;

import java.net.URI;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.Set;

/**
 * Created by mbulava on 5/6/2015.
 */
public abstract class ConnectToProjectWorkspaceTask extends BackgroundableTask {
    private static final Logger log = FileLogger.getInstance(ConnectToProjectWorkspaceTask.class);

    private Credentials credentials;
    private TFSTeamProjectCollection connection;
    private Workspace workspace;
    private final boolean forceOnline;

    public ConnectToProjectWorkspaceTask(Project project) {
        this(project, false);
    }

    public ConnectToProjectWorkspaceTask(Project project, boolean forceOnline){
        this(project, null, forceOnline);
    }

    public ConnectToProjectWorkspaceTask(Project project, Credentials credentials, boolean forceOnline){
        super(project, Messages.getString("ConnectToProjectWorkspace.Title"), true);
        Check.notNull(project, "project");
        this.credentials = credentials;
        this.forceOnline = forceOnline;
    }

    @Override
    public void run(ProgressIndicator indicator) {
        //Check if the Project folder is mapped to a workspace
        boolean shouldTryConnection = false;
        ProjectPreferences pref = ProjectPreferences.getProjectInstance(myProject);
        if (pref.getReconnectAtStartup()){
            AbstractVcs myVcs = VcsUtil.getVcsFor(myProject, myProject.getBaseDir());
            if (myVcs != null){
                if(myVcs instanceof TFSVcs){
                    if (!pref.getProjectIsOffline() || forceOnline){
                        shouldTryConnection = true;
                    }
                } else {
                    log.debug("Project is under a different Source Code provider");
                }
            } else {
                log.debug("Project is not under source control..");
            }
        }

        if (shouldTryConnection){
            WorkspaceInfo myWorkspaceInfo = Workstation.getCurrent(IdeaPersistenceStoreProvider.INSTANCE)
                    .getLocalWorkspaceInfo(myProject.getBaseDir().getPath());

            if (myWorkspaceInfo != null){
                processWithWorkspaceInfo(myWorkspaceInfo, indicator);
            } else {
                //this is the longer route because we need to check each connection
                indicator.setText2("Checking existing connections to see if path is mapped.");
                FilePath basePath = VcsUtil.getFilePath(myProject.getBaseDir().getPath(), true);
                ServerListManager manager = ServerListManagerFactory.getServerListProvider(IdeaPersistenceStoreProvider.INSTANCE);
                if (manager.getServerList().getServers().size() > 0){
                    Set<ServerListConfigurationEntry> configServers = manager.getServerList().getServers();
                    Iterator<ServerListConfigurationEntry> EntryIterator = configServers.iterator();
                    while (EntryIterator.hasNext()){
                        ServerListConfigurationEntry entry = EntryIterator.next();
                        Iterator<ServerListCollectionEntry> collectionIterator = entry.getCollections().iterator();
                        while (collectionIterator.hasNext()){
                            ServerListCollectionEntry collectionEntry = collectionIterator.next();
                            URI serverURI = collectionEntry.getURI();

                            try{
                                CommonProcessor(serverURI, indicator);
                            } catch (Throwable tr){
                                tr.printStackTrace();
                            }

                            if (this.connection != null){
                                Workspace[] workspaces = connection.getVersionControlClient().queryWorkspaces(null,
                                        connection.getAuthenticatedIdentity().getUniqueName(), LocalHost.getShortName());
                                for(int i = 0; i < workspaces.length; i++){
                                    String[] paths =  workspaces[i].getMappedPaths();
                                    for(int j = 0; j <paths.length; j++){
                                        if (basePath.isUnder(VcsUtil.getFilePath(paths[j]), false)){
                                            this.workspace = workspaces[i];
                                            return;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

    }

    private void checkCredentials(URI serverURI, ProgressIndicator indicator){
        //Get the Credentials.
        if (this.credentials == null){

            CachedCredentials cachedCredentials = CredentialsManagerFactory
                    .getCredentialsManager(IdeaPersistenceStoreProvider.INSTANCE).getCredentials(serverURI);
            this.credentials = cachedCredentials != null ? cachedCredentials.toCredentials() :
                    new DefaultNTCredentials();
        }

        if (this.credentials == null || CredentialsUtils.needsPassword(credentials)) {
            CredentialsDialog credentialsDialog  = new CredentialsDialog(myProject, serverURI);
            credentialsDialog.setCredentials(credentials);
            credentialsDialog.setAllowSavePassword(CredentialsManagerFactory
                    .getCredentialsManager(IdeaPersistenceStoreProvider.INSTANCE).canWrite());
            if (UIHelpers.openOnUIThread(credentialsDialog) != DialogWrapper.OK_EXIT_CODE)
                indicator.cancel();
            this.credentials = credentialsDialog.getCredentials();
        }
        indicator.checkCanceled();
    }

    private void CommonProcessor(URI serverURI, ProgressIndicator indicator){
        ConnectionAdvisor connectionAdvisor = new ClientConnectionAdvisor();

        checkCredentials(serverURI, indicator);
        //Start the connection
        String username = getUsername();
        String message;
        if (username != null) {
            String messageFormat = Messages.getString("ConnectToConfigurationServerCommand.ConnectingToServerAsFormat");
            message = MessageFormat.format(messageFormat, new Object[]{serverURI.toString(), username});
        } else {
            String messageFormat = Messages.getString("ConnectToConfigurationServerCommand.ConnectionToServerFormat");
            message = MessageFormat.format(messageFormat, new Object[] { serverURI.toString() });
        }
        log.info(message);
        indicator.setText(message);

        try{
            this.connection = new TFSTeamProjectCollection(serverURI, this.credentials,
                    connectionAdvisor);
            this.connection.authenticate();
            return;
        } catch (Exception e){
            if (this.connection != null){
                this.credentials = this.connection.getCredentials();
                this.connection.close();
                this.connection = null;
            }
            if (((e instanceof TFSUnauthorizedException)) || ((e instanceof TFSAccessException)) ||
                    ((e instanceof TFSFederatedAuthException))) {
                throw e;
            }
            if (e instanceof TransportRequestHandlerCanceledException)
                indicator.cancel();

            //if (connectionTypes.size() == 0)
            //    throw e;

            String messageFormat = "Server not found at {0}";

            message = MessageFormat.format("Server not found at {0}", new Object[] { e.getLocalizedMessage() });
            log.info(message);
            throw e;
        }
        //indicator.checkCanceled();

    }

    private void processWithWorkspaceInfo(WorkspaceInfo myWorkspaceInfo, ProgressIndicator indicator){
        URI serverURI = myWorkspaceInfo.getServerURI();


        CommonProcessor(serverURI, indicator);

        //get the workspace
        //String machineName = LocalHost.getShortName();
        //Workspace[] workspaces = this.connection.getVersionControlClient().queryWorkspaces(null, ".", machineName)
        this.workspace = myWorkspaceInfo.getWorkspace(connection);
    }

    private String getUsername() {
        if ((this.credentials != null) && ((this.credentials instanceof UsernamePasswordCredentials)))
            return ((UsernamePasswordCredentials)this.credentials).getUsername();

        return null;
    }

    private Workspace findWorkspace(String name, Workspace[] workspaces) {
        if ((name == null) || (workspaces == null) || (workspaces.length < 1)) {
            return null;
        }
        for (int i = 0; i < workspaces.length; i++) {
            if (workspaces[i].getName().equalsIgnoreCase(name)) {
                return workspaces[i];
            }
        }
        return null;
    }

    public TFSTeamProjectCollection getConnection(){
        return connection;
    }

    public Workspace getWorkspace(){
        return this.workspace;
    }


    private static final class ConnectionURIAndType {
        private final Class<? extends TFSConnection> type;
        private final URI uri;

        public ConnectionURIAndType(Class<? extends TFSConnection> type, URI uri) {
            Check.notNull(type, "type");
            Check.notNull(uri, "uri");

            this.type = type;
            this.uri = uri;
        }

        public Class<? extends TFSConnection> getType() {
            return this.type;
        }

        public URI getURI() {
            return this.uri;
        }
    }

}
